#ifndef PW_PBL_META_VLUINT_HPP
#define PW_PBL_META_VLUINT_HPP

#include <algorithm>
#include <concepts>
#include <cassert>

namespace pw_pbl::Meta
{
	namespace _vluint
	{
		template <std::unsigned_integral CELL, size_t N>
		struct Encoded
		{
			CELL _size;
			CELL _data[N];

			static constexpr auto ECellSize = sizeof(CELL);
			constexpr auto *data() const noexcept { return _data; }
			constexpr size_t size() const noexcept { return _size; }
			constexpr auto *begin() const noexcept { return _data; }
			constexpr auto *end() const noexcept { return &_data[_size]; }
		};

		template <std::unsigned_integral CELL, Encoded v>
		constexpr std::array<CELL, v.size()> toArray() noexcept
		{
			std::array<CELL, v.size()> result;
			std::copy_n(v.begin(), v.size(), result.begin());
			return result;
		}

		template <std::unsigned_integral CELL, std::unsigned_integral TGT>
		constexpr auto encode(TGT value) noexcept
		{
			if constexpr (sizeof(CELL) >= sizeof(TGT))
				return Encoded<CELL, 1>{._size = 1, ._data{value}};
			else
			{
				constexpr auto EGroupBits = sizeof(CELL) * CHAR_BIT - 1;
				constexpr CELL EHighMask = 1u << EGroupBits;
				constexpr size_t EMaxCount = (sizeof(TGT) * CHAR_BIT + EGroupBits - 1) / EGroupBits;
				Encoded<CELL, EMaxCount> out{};
				for (size_t i = 0; i < EMaxCount; i++, value >>= EGroupBits)
					if (value < EHighMask)
						return out._size = i + 1, out._data[i] = value, out;
					else
						out._data[i] = value | EHighMask;
				out._size = EMaxCount;
				return out;
			}
		};

		template <std::unsigned_integral CELL, std::unsigned_integral TGT>
		constexpr TGT decode(const CELL *p)
		{
			if constexpr (sizeof(CELL) >= sizeof(TGT))
				return *p;
			else
			{
				constexpr auto EGroupBits = sizeof(CELL) * CHAR_BIT - 1;
				constexpr CELL EHighMask = 1u << EGroupBits;
				if (!(*p & EHighMask))
					return *p;
				constexpr CELL EValueMask = (CELL)~EHighMask;
				TGT value = 0;
				TGT shift = 0;
				while (true)
				{
					value += TGT(*p & EValueMask) << shift;
					++p;
					if (!(*p & EHighMask))
						return value + (TGT(*p) << (shift + EGroupBits));
					shift += EGroupBits;
					if (sizeof(TGT) * CHAR_BIT <= shift)
						return 0;
				}
			}
		}
	} // namespace _vluint

	template <std::unsigned_integral CELL, std::unsigned_integral TGT, TGT... VALUE>
	struct VLUInt;

	template <std::unsigned_integral CELL, std::unsigned_integral TGT>
	struct VLUInt<CELL, TGT>
	{
		using cell_type = CELL;
		using tgt_calc_type = std::conditional_t<sizeof(TGT) <= sizeof(uintptr_t), uintptr_t, TGT>;

		inline auto *data() noexcept { return reinterpret_cast<cell_type *>(this); }
		inline auto *data() const noexcept { return reinterpret_cast<const cell_type *>(this); }

		static constexpr auto encode(TGT value) noexcept { return _vluint::encode<cell_type, tgt_calc_type>(value); }

		static constexpr auto decode(const cell_type *p) { return _vluint::decode<cell_type, tgt_calc_type>(p); }

		inline auto value() const noexcept { return decode(data()); }

		inline void *end() noexcept { return data() + size(); }

		inline const void *end() const noexcept { return data() + size(); }

		inline size_t size() const
		{
			if constexpr (sizeof(CELL) >= sizeof(TGT))
				return 1;
			else
			{
				constexpr CELL EHighMask = 1u << (sizeof(CELL) * CHAR_BIT - 1);
				for (size_t i = 0; true;)
					if (!(data()[i++] & EHighMask))
						return i;
			}
		}

		template <size_t N>
		inline void assign(const _vluint::Encoded<CELL, N> &v) noexcept { std::copy_n(v.begin(), v.size(), data()); }
		inline void assign(TGT v) noexcept
		{
			auto encoded = encode(v);
			assign(encoded);
			return encoded.size;
		}

		template <TGT VALUE>
		inline void assign(const VLUInt<CELL, TGT, VALUE> &v) noexcept { std::copy_n(v.begin(), v.size(), data()); }

	protected:
		VLUInt() = default;
		VLUInt(VLUInt &&) = default;
		VLUInt(const VLUInt &) = default;
	};

	template <std::unsigned_integral CELL, std::unsigned_integral TGT, TGT VALUE>
	struct VLUInt<CELL, TGT, VALUE> : public VLUInt<CELL, TGT>
	{
		using cell_type = CELL;
		static constexpr auto cdata = _vluint::toArray<cell_type, VLUInt<cell_type, TGT>::encode(VALUE)>();

		const std::array<cell_type, cdata.size()> data;

		constexpr VLUInt() noexcept : data{cdata} {}

	private:
		using VLUInt<CELL, TGT>::assign;
	};
} // namespace pw_pbl::Meta

#endif // PW_PBL_META_VLUINT_HPP
